"""
SocketChannel, implements the channel interface on a socket object.

Copyright (c) 2009 Christopher Hesse
http://code.google.com/p/python-expect/
"""

import Queue
import logging
from expect import is_windows
from expect.selectable import Selectable
from expect.exceptions import ChannelClosed
from ..base import BaseChannel
from .iothread import IOThread

log = logging.getLogger(__name__)


class SocketChannel(BaseChannel):
    """
    This channel reads and writes from a socket object.
    
    socket: the socket object to use
    buffering: if set to False, flush all writes immediately
    """
    
    def __init__(self, socket, **kwargs):
        super(SocketChannel, self).__init__(**kwargs)
        self.socket = socket
        self._closed = False
        
        self.read_selectable = Selectable()
        self.read_queue = Queue.Queue()
        self.write_selectable = Selectable()
        self.write_queue = Queue.Queue()
        
        io_thread = IOThread(self.name, self.socket, self.write_queue, self.write_selectable,
                             self.read_queue, self.read_selectable)
        io_thread.start()
        
    def __del__(self):
        # this is so the IOThread terminates if it has not already
        if hasattr(self, 'socket'):
            self.socket.close()
    
    def flush(self):
        if self.closed:
            raise ChannelClosed("Attempted to flush on a closed channel")
        else:
            self.write_queue.join()
        
    def fileno(self):
        return self.read_selectable.fileno()

    def write(self, data):
        if self.closed:
            raise ChannelClosed("Attempted to write to a closed channel")

        with self.write_selectable:
            self.write_queue.put(data)
            self.write_selectable.set()

    def read(self):
        if self.closed:
            return ''

        # gather all the items from the read queue
        buffers = []
        with self.read_selectable:
            for i in xrange(self.read_queue.qsize()):
                item = self.read_queue.get_nowait()
                # got channel closed special item
                if item is ChannelClosed:
                    self.close()
                    return ''.join(buffers)
                buffers.append(item)
            self.read_selectable.clear()
        data = ''.join(buffers)
        return data

    def close(self):
        self.socket.close()
        # the channel should be always readable when closed
        with self.read_selectable:
            self.read_selectable.set()
        self._closed = True
        log.debug("closed channel", **self._log_info())

    @property
    def closed(self):
        return self._closed